package com.quartz.monitor4job.util;
/**
 * Copyright 2008, David Robert Nadeau, NadeauSoftware.com
 *
 * This file is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 2 of
 * the License, or (at your option) any later version.
 *
 * This file is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this file.  If not, see
 * <a href="http://www.gnu.org/licenses">GNU Licenses</a>.
 */

import java.lang.management.ManagementFactory;
import java.lang.management.MonitorInfo;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;

/**
 * The ThreadUtilities class provides a collection of static methods for listing
 * and finding Thread, ThreadGroup, and ThreadInfo objects.
 * <p>
 * Please see the accompanying article: <a href=
 * "http://nadeausoftware.com/articles/2008/04/java_tip_how_list_and_find_threads_and_thread_groups"
 * >Java tip: How to list and find threads and thread groups</a>
 *
 * @author <a href="http://NadeauSoftware.com/">David R. Nadeau</a>
 */
public final class ThreadUtilities {
	// Dummy constructor
	/**
	 * The ThreadUtilities class cannot be instanced.
	 */
	private ThreadUtilities() {
	}

	// Thread groups
	/**
	 * The root thread group saved on the first search for it. The root group
	 * doesn't change for the life of the JVM, so once found there is no need to
	 * find it again.
	 */
	private static ThreadGroup rootThreadGroup = null;

	/**
	 * Get the root thread group in the thread group tree. Since there is always
	 * a root thread group, this method never returns null.
	 *
	 * @return the root thread group
	 */
	public static ThreadGroup getRootThreadGroup() {
		if (rootThreadGroup != null)
			return rootThreadGroup;

		ThreadGroup tg = Thread.currentThread().getThreadGroup();
		ThreadGroup ptg;
		while ((ptg = tg.getParent()) != null)
			tg = ptg;
		rootThreadGroup = tg;
		return tg;
	}

	/**
	 * Get a list of all thread groups. Since there is always at least one
	 * thread group (the root), this method never returns a null or empty array.
	 *
	 * @return an array of thread groups
	 */
	public static ThreadGroup[] getAllThreadGroups() {
		final ThreadGroup root = getRootThreadGroup();
		int nAlloc = root.activeGroupCount();
		int n = 0;
		ThreadGroup[] groups = null;
		do {
			nAlloc *= 2;
			groups = new ThreadGroup[nAlloc];
			n = root.enumerate(groups, true);
		} while (n == nAlloc);
		ThreadGroup[] allGroups = new ThreadGroup[n + 1];
		allGroups[0] = root;
		System.arraycopy(groups, 0, allGroups, 1, n);
		return allGroups;
	}

	/**
	 * Get the thread group with the given name. A null is returned if no such
	 * group is found. If more than one group has the same name, the first one
	 * found is returned.
	 *
	 * @param name
	 *            the thread group name to search for
	 * @return the thread group, or null if not found
	 * @throws NullPointerException
	 *             if the name is null
	 */
	public static ThreadGroup getThreadGroup(final String name) {
		if (name == null)
			throw new NullPointerException("Null name");
		final ThreadGroup[] groups = getAllThreadGroups();
		for (ThreadGroup group : groups)
			if (group.getName().equals(name))
				return group;
		return null;
	}

	// Threads
	/**
	 * Get a list of all threads. Since there is always at least one thread,
	 * this method never returns null or an empty array.
	 *
	 * @return an array of threads
	 */
	public static Thread[] getAllThreads() {
		final ThreadGroup root = getRootThreadGroup();
		final ThreadMXBean thbean = ManagementFactory.getThreadMXBean();
		int nAlloc = thbean.getThreadCount();
		int n = 0;
		Thread[] threads = null;
		do {
			nAlloc *= 2;
			threads = new Thread[nAlloc];
			n = root.enumerate(threads, true);
		} while (n == nAlloc);
		return java.util.Arrays.copyOf(threads, n);
	}

	/**
	 * Get a list of all threads in a thread group. An empty array is returned
	 * if there are no threads in the group.
	 *
	 * @param group
	 *            the thread group to list
	 * @return an array of threads
	 * @throws NullPointerException
	 *             if the group is null
	 */
	public static Thread[] getGroupThreads(final ThreadGroup group) {
		if (group == null)
			throw new NullPointerException("Null group");
		int nAlloc = group.activeCount();
		int n = 0;
		Thread[] threads = null;
		do {
			nAlloc *= 2;
			threads = new Thread[nAlloc];
			n = group.enumerate(threads, false);
		} while (n == nAlloc);
		return java.util.Arrays.copyOf(threads, n);
	}

	/**
	 * Get a list of all threads in a named thread group. A null is returned if
	 * the group cannot be found. An empty array is returned if there are no
	 * threads in the group.
	 *
	 * @param name
	 *            the name of the thread group
	 * @return an array of threads, or null if the group is not found
	 * @throws NullPointerException
	 *             if the name is null
	 */
	public static Thread[] getGroupThreads(final String name) {
		final ThreadGroup group = getThreadGroup(name);
		if (group == null)
			return null;
		return getGroupThreads(group);
	}

	/**
	 * Get a list of all threads, sorted from highest to lowest priority. Since
	 * there is always at least one thread, this method never returns null or an
	 * empty array. Since threads may change their priority during this method's
	 * sort, the returned thread list may not be correct by the time it is
	 * returned.
	 *
	 * @return an array of threads
	 */
	public static Thread[] getAllThreadsPrioritized() {
		final Thread[] allThreads = getAllThreads();
		java.util.Arrays.sort(allThreads, new java.util.Comparator<Thread>() {
			public int compare(final Thread t1, final Thread t2) {
				return t2.getPriority() - t1.getPriority();
			}
		});
		return allThreads;
	}

	/**
	 * Get a list of all daemon threads. An empty array is returned if there are
	 * no daemon threads.
	 *
	 * @return an array of daemon threads
	 */
	public static Thread[] getAllDaemonThreads() {
		final Thread[] allThreads = getAllThreads();
		final Thread[] daemons = new Thread[allThreads.length];
		int nDaemon = 0;
		for (Thread thread : allThreads)
			if (thread.isDaemon())
				daemons[nDaemon++] = thread;
		return java.util.Arrays.copyOf(daemons, nDaemon);
	}

	/**
	 * Get a list of all threads with a given thread state. Thread states are
	 * defined in the Thread.State enum for the Thread class. Principal thread
	 * states include RUNNABLE, WAITING, TIMED_WAITING, and BLOCKED. An empty
	 * array is returned if there are no threads in the chosen state.
	 *
	 * @param state
	 *            the state to look for
	 * @return an array of threads in that state
	 */
	public static Thread[] getAllThreads(final Thread.State state) {
		final Thread[] allThreads = getAllThreads();
		final Thread[] found = new Thread[allThreads.length];
		int nFound = 0;
		for (Thread thread : allThreads)
			if (thread.getState() == state)
				found[nFound++] = thread;
		return java.util.Arrays.copyOf(found, nFound);
	}

	/**
	 * Get the thread with the given name. A null is returned if no such thread
	 * is found. If more than one thread has the same name, the first one found
	 * is returned.
	 *
	 * @param name
	 *            the thread name to search for
	 * @return the thread, or null if not found
	 * @throws NullPointerException
	 *             if the name is null
	 */
	public static Thread getThread(final String name) {
		if (name == null)
			throw new NullPointerException("Null name");
		final Thread[] threads = getAllThreads();
		for (Thread thread : threads)
			if (thread.getName().equals(name))
				return thread;
		return null;
	}

	/**
	 * Get the thread with the given ID. A null is returned if no such thread is
	 * found.
	 *
	 * @param id
	 *            the thread ID to search for
	 * @return the thread, or null if not found
	 */
	public static Thread getThread(final long id) {
		final Thread[] threads = getAllThreads();
		for (Thread thread : threads)
			if (thread.getId() == id)
				return thread;
		return null;
	}

	/**
	 * Get the thread for the given thread info. A null is returned if the
	 * thread cannot be found.
	 *
	 * @param info
	 *            the thread info to search for
	 * @return the thread, or null if not found
	 * @throws NullPointerException
	 *             if info is null
	 */
	public static Thread getThread(final ThreadInfo info) {
		if (info == null)
			throw new NullPointerException("Null info");
		return getThread(info.getThreadId());
	}

	// ThreadInfo
	/**
	 * Get a list of all thread info objects. Since there is always at least one
	 * thread running, there is always at least one thread info object. This
	 * method never returns a null or empty array.
	 *
	 * @return an array of thread infos
	 */
	public static ThreadInfo[] getAllThreadInfos() {
		final ThreadMXBean thbean = ManagementFactory.getThreadMXBean();
		final long[] ids = thbean.getAllThreadIds();

		// Get thread info with lock info, when available.
		ThreadInfo[] infos;
		if (!thbean.isObjectMonitorUsageSupported() || !thbean.isSynchronizerUsageSupported())
			infos = thbean.getThreadInfo(ids);
		else
			infos = thbean.getThreadInfo(ids, true, true);

		// Clean nulls from array if threads have died.
		final ThreadInfo[] notNulls = new ThreadInfo[infos.length];
		int nNotNulls = 0;
		for (ThreadInfo info : infos)
			if (info != null)
				notNulls[nNotNulls++] = info;
		if (nNotNulls == infos.length)
			return infos; // Original had no nulls
		return java.util.Arrays.copyOf(notNulls, nNotNulls);
	}

	/**
	 * Get the thread info for the thread with the given name. A null is
	 * returned if no such thread info is found. If more than one thread has the
	 * same name, the thread info for the first one found is returned.
	 *
	 * @param name
	 *            the thread name to search for
	 * @return the thread info, or null if not found
	 * @throws NullPointerException
	 *             if the name is null
	 */
	public static ThreadInfo getThreadInfo(final String name) {
		if (name == null)
			throw new NullPointerException("Null name");

		final Thread[] threads = getAllThreads();
		for (Thread thread : threads)
			if (thread.getName().equals(name))
				return getThreadInfo(thread.getId());
		return null;
	}

	/**
	 * Get the thread info for the thread with the given ID. A null is returned
	 * if no such thread info is found.
	 *
	 * @param id
	 *            the thread ID to search for
	 * @return the thread info, or null if not found
	 * @throws IllegalArgumentException
	 *             if id <= 0
	 */
	public static ThreadInfo getThreadInfo(final long id) {
		final ThreadMXBean thbean = ManagementFactory.getThreadMXBean();

		// Get thread info with lock info, when available.
		if (!thbean.isObjectMonitorUsageSupported() || !thbean.isSynchronizerUsageSupported())
			return thbean.getThreadInfo(id);

		final ThreadInfo[] infos = thbean.getThreadInfo(new long[]{id}, true, true);
		if (infos.length == 0)
			return null;
		return infos[0];
	}

	public static ThreadInfo getExtendThreadInfo(final long threadID) {
		ThreadInfo ti = null;
		final ThreadMXBean threadMBean = ManagementFactory.getThreadMXBean();
		if (threadMBean.isObjectMonitorUsageSupported()) {
			// VMs that support the monitor usage monitoring
			ThreadInfo[] infos = threadMBean.dumpAllThreads(true, false);
			for (ThreadInfo info : infos) {
				if (info.getThreadId() == threadID) {
					ti = info;
					break;
				}
			}
		} else {
			// VM doesn't support monitor usage monitoring
			ti = threadMBean.getThreadInfo(threadID, Integer.MAX_VALUE);
		}
		return ti;
	}

	/**
	 * Get the thread info for the given thread. A null is returned if the
	 * thread info cannot be found.
	 *
	 * @param thread
	 *            the thread to search for
	 * @return the thread info, or null if not found
	 * @throws NullPointerException
	 *             if thread is null
	 */
	public static ThreadInfo getThreadInfo(final Thread thread) {
		if (thread == null)
			throw new NullPointerException("Null thread");
		return getThreadInfo(thread.getId());
	}

	// MonitorInfo and LockInfo
	/**
	 * Get the thread holding a lock on the given object. A null is returned if
	 * there is no such thread.
	 *
	 * @param object
	 *            the object to look for a lock on
	 * @return the thread holding a lock, or null if there is none
	 * @throws NullPointerException
	 *             if the object is null
	 */
	public static Thread getLockingThread(final Object object) {
		if (object == null)
			throw new NullPointerException("Null object");
		final long identity = System.identityHashCode(object);

		final Thread[] allThreads = getAllThreads();
		ThreadInfo info = null;
		MonitorInfo[] monitors = null;
		for (Thread thread : allThreads) {
			info = getThreadInfo(thread.getId());
			if (info == null)
				continue;
			monitors = info.getLockedMonitors();
			for (MonitorInfo monitor : monitors)
				if (identity == monitor.getIdentityHashCode())
					return thread;
		}
		return null;
	}

	/**
	 * Get the thread who's lock is blocking the given thread. A null is
	 * returned if there is no such thread.
	 *
	 * @param blockedThread
	 *            the blocked thread
	 * @return the blocking thread, or null if there is none
	 * @throws NullPointerException
	 *             if the blocked thread is null
	 */
	public static Thread getBlockingThread(final Thread blockedThread) {
		final ThreadInfo info = getThreadInfo(blockedThread);
		if (info == null)
			return null;
		final long id = info.getLockOwnerId();
		if (id == -1)
			return null;
		return getThread(id);
	}
}